1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package storm.starter.tools;
19  
20  import com.google.common.base.Throwables;
21  import com.google.common.collect.ImmutableList;
22  import com.google.common.collect.Lists;
23  import org.jmock.lib.concurrent.Blitzer;
24  import org.testng.annotations.DataProvider;
25  import org.testng.annotations.Test;
26  
27  import java.util.List;
28  
29  import static org.fest.assertions.api.Assertions.assertThat;
30  
31  public class RankingsTest {
32  
33    private static final int ANY_TOPN = 42;
34    private static final Rankable ANY_RANKABLE = new RankableObjectWithFields("someObject", ANY_TOPN);
35    private static final Rankable ZERO = new RankableObjectWithFields("ZERO_COUNT", 0);
36    private static final Rankable A = new RankableObjectWithFields("A", 1);
37    private static final Rankable B = new RankableObjectWithFields("B", 2);
38    private static final Rankable C = new RankableObjectWithFields("C", 3);
39    private static final Rankable D = new RankableObjectWithFields("D", 4);
40    private static final Rankable E = new RankableObjectWithFields("E", 5);
41    private static final Rankable F = new RankableObjectWithFields("F", 6);
42    private static final Rankable G = new RankableObjectWithFields("G", 7);
43    private static final Rankable H = new RankableObjectWithFields("H", 8);
44  
45    @DataProvider
46    public Object[][] illegalTopNData() {
47      return new Object[][]{ { 0 }, { -1 }, { -2 }, { -10 } };
48    }
49  
50    @Test(expectedExceptions = IllegalArgumentException.class, dataProvider = "illegalTopNData")
51    public void constructorWithNegativeOrZeroTopNShouldThrowIAE(int topN) {
52      new Rankings(topN);
53    }
54  
55    @DataProvider
56    public Object[][] copyRankingsData() {
57      return new Object[][]{ { 5, Lists.newArrayList(A, B, C) }, { 2, Lists.newArrayList(A, B, C, D) },
58          { 1, Lists.newArrayList() }, { 1, Lists.newArrayList(A) }, { 1, Lists.newArrayList(A, B) } };
59    }
60  
61    @Test(dataProvider = "copyRankingsData")
62    public void copyConstructorShouldReturnCopy(int topN, List<Rankable> rankables) {
63      // given
64      Rankings rankings = new Rankings(topN);
65      for (Rankable r : rankables) {
66        rankings.updateWith(r);
67      }
68  
69      // when
70      Rankings copy = new Rankings(rankings);
71  
72      // then
73      assertThat(copy.maxSize()).isEqualTo(rankings.maxSize());
74      assertThat(copy.getRankings()).isEqualTo(rankings.getRankings());
75    }
76  
77    @DataProvider
78    public Object[][] defensiveCopyRankingsData() {
79      return new Object[][]{ { 5, Lists.newArrayList(A, B, C), Lists.newArrayList(D) }, { 2, Lists.newArrayList(A, B, C,
80          D), Lists.newArrayList(E, F) }, { 1, Lists.newArrayList(), Lists.newArrayList(A) }, { 1, Lists.newArrayList(A),
81          Lists.newArrayList(B) }, { 1, Lists.newArrayList(ZERO), Lists.newArrayList(B) }, { 1, Lists.newArrayList(ZERO),
82          Lists.newArrayList() } };
83    }
84  
85    @Test(dataProvider = "defensiveCopyRankingsData")
86    public void copyConstructorShouldReturnDefensiveCopy(int topN, List<Rankable> rankables, List<Rankable> changes) {
87      // given
88      Rankings original = new Rankings(topN);
89      for (Rankable r : rankables) {
90        original.updateWith(r);
91      }
92      int expSize = original.size();
93      List<Rankable> expRankings = original.getRankings();
94  
95      // when
96      Rankings copy = new Rankings(original);
97      for (Rankable r : changes) {
98        copy.updateWith(r);
99      }
100 
101     // then
102     assertThat(original.size()).isEqualTo(expSize);
103     assertThat(original.getRankings()).isEqualTo(expRankings);
104   }
105 
106   @DataProvider
107   public Object[][] legalTopNData() {
108     return new Object[][]{ { 1 }, { 2 }, { 1000 }, { 1000000 } };
109   }
110 
111   @Test(dataProvider = "legalTopNData")
112   public void constructorWithPositiveTopNShouldBeOk(int topN) {
113     // given/when
114     Rankings rankings = new Rankings(topN);
115 
116     // then
117     assertThat(rankings.maxSize()).isEqualTo(topN);
118   }
119 
120   @Test
121   public void shouldHaveDefaultConstructor() {
122     new Rankings();
123   }
124 
125   @Test
126   public void defaultConstructorShouldSetPositiveTopN() {
127     // given/when
128     Rankings rankings = new Rankings();
129 
130     // then
131     assertThat(rankings.maxSize()).isGreaterThan(0);
132   }
133 
134   @DataProvider
135   public Object[][] rankingsGrowData() {
136     return new Object[][]{ { 2, Lists.newArrayList(new RankableObjectWithFields("A", 1), new RankableObjectWithFields(
137         "B", 2), new RankableObjectWithFields("C", 3)) }, { 2, Lists.newArrayList(new RankableObjectWithFields("A", 1),
138         new RankableObjectWithFields("B", 2), new RankableObjectWithFields("C", 3), new RankableObjectWithFields("D",
139         4)) } };
140   }
141 
142   @Test(dataProvider = "rankingsGrowData")
143   public void sizeOfRankingsShouldNotGrowBeyondTopN(int topN, List<Rankable> rankables) {
144     // sanity check of the provided test data
145     assertThat(rankables.size()).overridingErrorMessage(
146         "The supplied test data is not correct: the number of rankables <%d> should be greater than <%d>",
147         rankables.size(), topN).isGreaterThan(topN);
148 
149     // given
150     Rankings rankings = new Rankings(topN);
151 
152     // when
153     for (Rankable r : rankables) {
154       rankings.updateWith(r);
155     }
156 
157     // then
158     assertThat(rankings.size()).isLessThanOrEqualTo(rankings.maxSize());
159   }
160 
161   @DataProvider
162   public Object[][] simulatedRankingsData() {
163     return new Object[][]{ { Lists.newArrayList(A), Lists.newArrayList(A) }, { Lists.newArrayList(B, D, A, C),
164         Lists.newArrayList(D, C, B, A) }, { Lists.newArrayList(B, F, A, C, D, E), Lists.newArrayList(F, E, D, C, B,
165         A) }, { Lists.newArrayList(G, B, F, A, C, D, E, H), Lists.newArrayList(H, G, F, E, D, C, B, A) } };
166   }
167 
168   @Test(dataProvider = "simulatedRankingsData")
169   public void shouldCorrectlyRankWhenUpdatedWithRankables(List<Rankable> unsorted, List<Rankable> expSorted) {
170     // given
171     Rankings rankings = new Rankings(unsorted.size());
172 
173     // when
174     for (Rankable r : unsorted) {
175       rankings.updateWith(r);
176     }
177 
178     // then
179     assertThat(rankings.getRankings()).isEqualTo(expSorted);
180   }
181 
182   @Test(dataProvider = "simulatedRankingsData")
183   public void shouldCorrectlyRankWhenEmptyAndUpdatedWithOtherRankings(List<Rankable> unsorted,
184       List<Rankable> expSorted) {
185     // given
186     Rankings rankings = new Rankings(unsorted.size());
187     Rankings otherRankings = new Rankings(rankings.maxSize());
188     for (Rankable r : unsorted) {
189       otherRankings.updateWith(r);
190     }
191 
192     // when
193     rankings.updateWith(otherRankings);
194 
195     // then
196     assertThat(rankings.getRankings()).isEqualTo(expSorted);
197   }
198 
199   @Test(dataProvider = "simulatedRankingsData")
200   public void shouldCorrectlyRankWhenUpdatedWithEmptyOtherRankings(List<Rankable> unsorted, List<Rankable> expSorted) {
201     // given
202     Rankings rankings = new Rankings(unsorted.size());
203     for (Rankable r : unsorted) {
204       rankings.updateWith(r);
205     }
206     Rankings emptyRankings = new Rankings(ANY_TOPN);
207 
208     // when
209     rankings.updateWith(emptyRankings);
210 
211     // then
212     assertThat(rankings.getRankings()).isEqualTo(expSorted);
213   }
214 
215   @DataProvider
216   public Object[][] simulatedRankingsAndOtherRankingsData() {
217     return new Object[][]{ { Lists.newArrayList(A), Lists.newArrayList(A), Lists.newArrayList(A) },
218         { Lists.newArrayList(A, C), Lists.newArrayList(B, D), Lists.newArrayList(D, C, B, A) }, { Lists.newArrayList(B,
219         F, A), Lists.newArrayList(C, D, E), Lists.newArrayList(F, E, D, C, B, A) }, { Lists.newArrayList(G, B, F, A, C),
220         Lists.newArrayList(D, E, H), Lists.newArrayList(H, G, F, E, D, C, B, A) } };
221   }
222 
223   @Test(dataProvider = "simulatedRankingsAndOtherRankingsData")
224   public void shouldCorrectlyRankWhenNotEmptyAndUpdatedWithOtherRankings(List<Rankable> unsorted,
225       List<Rankable> unsortedForOtherRankings, List<Rankable> expSorted) {
226     // given
227     Rankings rankings = new Rankings(expSorted.size());
228     for (Rankable r : unsorted) {
229       rankings.updateWith(r);
230     }
231     Rankings otherRankings = new Rankings(unsortedForOtherRankings.size());
232     for (Rankable r : unsortedForOtherRankings) {
233       otherRankings.updateWith(r);
234     }
235 
236     // when
237     rankings.updateWith(otherRankings);
238 
239     // then
240     assertThat(rankings.getRankings()).isEqualTo(expSorted);
241   }
242 
243   @DataProvider
244   public Object[][] duplicatesData() {
245     Rankable A1 = new RankableObjectWithFields("A", 1);
246     Rankable A2 = new RankableObjectWithFields("A", 2);
247     Rankable A3 = new RankableObjectWithFields("A", 3);
248     return new Object[][]{ { Lists.newArrayList(ANY_RANKABLE, ANY_RANKABLE, ANY_RANKABLE) }, { Lists.newArrayList(A1,
249         A2, A3) }, };
250   }
251 
252   @Test(dataProvider = "duplicatesData")
253   public void shouldNotRankDuplicateObjectsMoreThanOnce(List<Rankable> duplicates) {
254     // given
255     Rankings rankings = new Rankings(duplicates.size());
256 
257     // when
258     for (Rankable r : duplicates) {
259       rankings.updateWith(r);
260     }
261 
262     // then
263     assertThat(rankings.size()).isEqualTo(1);
264   }
265 
266   @DataProvider
267   public Object[][] removeZeroRankingsData() {
268     return new Object[][]{ { Lists.newArrayList(A, ZERO), Lists.newArrayList(A) }, { Lists.newArrayList(A),
269         Lists.newArrayList(A) }, { Lists.newArrayList(ZERO, A), Lists.newArrayList(A) }, { Lists.newArrayList(ZERO),
270         Lists.newArrayList() }, { Lists.newArrayList(ZERO, new RankableObjectWithFields("ZERO2", 0)),
271         Lists.newArrayList() }, { Lists.newArrayList(B, ZERO, new RankableObjectWithFields("ZERO2", 0), D,
272         new RankableObjectWithFields("ZERO3", 0), new RankableObjectWithFields("ZERO4", 0), C), Lists.newArrayList(D, C,
273         B) }, { Lists.newArrayList(A, ZERO, B), Lists.newArrayList(B, A) } };
274   }
275 
276   @Test(dataProvider = "removeZeroRankingsData")
277   public void shouldRemoveZeroCounts(List<Rankable> unsorted, List<Rankable> expSorted) {
278     // given
279     Rankings rankings = new Rankings(unsorted.size());
280     for (Rankable r : unsorted) {
281       rankings.updateWith(r);
282     }
283 
284     // when
285     rankings.pruneZeroCounts();
286 
287     // then
288     assertThat(rankings.getRankings()).isEqualTo(expSorted);
289   }
290 
291   @Test
292   public void updatingWithNewRankablesShouldBeThreadSafe() throws InterruptedException {
293     // given
294     final List<Rankable> entries = ImmutableList.of(A, B, C, D);
295     final Rankings rankings = new Rankings(entries.size());
296 
297     // We are capturing exceptions thrown in Blitzer's child threads into this data structure so that we can properly
298     // pass/fail this test.  The reason is that Blitzer doesn't report exceptions, which is a known bug in Blitzer
299     // (JMOCK-263).  See https://github.com/jmock-developers/jmock-library/issues/22 for more information.
300     final List<Exception> exceptions = Lists.newArrayList();
301     Blitzer blitzer = new Blitzer(1000);
302 
303     // when
304     blitzer.blitz(new Runnable() {
305       public void run() {
306         for (Rankable r : entries) {
307           try {
308             rankings.updateWith(r);
309           }
310           catch (RuntimeException e) {
311             synchronized(exceptions) {
312               exceptions.add(e);
313             }
314           }
315         }
316       }
317     });
318     blitzer.shutdown();
319 
320     // then
321     //
322     if (!exceptions.isEmpty()) {
323       for (Exception e : exceptions) {
324         System.err.println(Throwables.getStackTraceAsString(e));
325       }
326     }
327     assertThat(exceptions).isEmpty();
328   }
329 
330   @Test(dataProvider = "copyRankingsData")
331   public void copyShouldReturnCopy(int topN, List<Rankable> rankables) {
332     // given
333     Rankings rankings = new Rankings(topN);
334     for (Rankable r : rankables) {
335       rankings.updateWith(r);
336     }
337 
338     // when
339     Rankings copy = rankings.copy();
340 
341     // then
342     assertThat(copy.maxSize()).isEqualTo(rankings.maxSize());
343     assertThat(copy.getRankings()).isEqualTo(rankings.getRankings());
344   }
345 
346   @Test(dataProvider = "defensiveCopyRankingsData")
347   public void copyShouldReturnDefensiveCopy(int topN, List<Rankable> rankables, List<Rankable> changes) {
348     // given
349     Rankings original = new Rankings(topN);
350     for (Rankable r : rankables) {
351       original.updateWith(r);
352     }
353     int expSize = original.size();
354     List<Rankable> expRankings = original.getRankings();
355 
356     // when
357     Rankings copy = original.copy();
358     for (Rankable r : changes) {
359       copy.updateWith(r);
360     }
361     copy.pruneZeroCounts();
362 
363     // then
364     assertThat(original.size()).isEqualTo(expSize);
365     assertThat(original.getRankings()).isEqualTo(expRankings);
366   }
367 
368 }